#ifndef SERIAL_PORT_MANAGER_HPP
#define SERIAL_PORT_MANAGER_HPP

#include <string>
#include <vector>
#include <windows.h>
#include <functional>

#include "HandShake.hpp"

// https://stackoverflow.com/questions/15794422/serial-port-rs-232-connection-in-c
// any of our command is splited by 0d0a
// the Windows API reads until 0x0a and omits it -> not really...what?

#ifndef U8Vec
#define U8Vec std::vector<uint8_t>
#endif

typedef void(__stdcall* SerialCallbackMP)(int index, bool success, const void* log, int logLen, const void* data, int dataLen);
//typedef void(__stdcall* SerialCallback)(bool success, const void* log, int logLen, const void* data, int dataLen);

class SerialPortManager {
public:
	struct HandShakeProtocolSerial : HandShakeProtocol {
		HandShakeProtocolSerial() {};
		HandShakeProtocolSerial(int shakeNum) { handshakes.resize(shakeNum); };
		int excute(SerialPortManager* manager);
	};

	SerialPortManager() {};
	SerialPortManager(void* handle);
	~SerialPortManager();

	bool isSimu();
	bool configSimu(bool flagSimu);
	bool makeShallowCopy(SerialPortManager* refManager);

	static std::vector<SerialPortManager*> listPortsWithHardwareIds(std::vector<std::wstring>& hwIds);
	static void resetUSBDevices();

	bool connectHWID(std::wstring hardwareIDIn, int boudRate, char parity, int dataBitNum, int stopBits, int portIdx = 0);
	bool connectHWIDWithHandshake(std::wstring hardwareIDIn,
		U8Vec content, U8Vec expectedReply,
		int waitAfterOpen, int handshakeTimeout,
		int baudRate, char parity, int dataBitNum, int stopBits);
	bool connectHWIDWithHandshakeDiffReplies(std::wstring hardwareIDIn,
		U8Vec content, std::vector<U8Vec> possibleReplies,
		int waitAfterOpen, int handshakeTimeout,
		int baudRate, char parity, int dataBitNum, int stopBits,
		int* replyIndexOut);
	int connectHWIDWithHandshakeOfIndex(std::wstring hardwareIDIn,
		U8Vec content, U8Vec expectedReply,
		int waitAfterOpen, int handshakeTimeout,
		int baudRate, char parity, int dataBitNum, int stopBits);
	int connectHWIDWithHandshake(std::wstring hardwareIDIn,
		int waitAfterOpen,
		HandShakeProtocolSerial& handshake,
		int baudRate, char parity, int dataBitNum, int stopBits);

	void simulateConnect(HandShakeProtocol& handshake,
		bool simu, int baudRate, char parity, int dataBitNum, int stopBits);
	void simulateHandshake(HandShakeProtocol& handshake);

	bool isConnected();
	bool rebootDriver();
	static bool rebootDrivers();

	bool config(int baudRate, char parity, int dataBitNum, int stopBits);

	std::string getPortName();
	std::string getConnectionConfig();

	bool setRts(int val);
	bool setDtr(int val);
	int readCts();
	int readDsr();
	bool purge();

	bool send(std::string content);
	bool send(U8Vec content);
	bool send(void* buffer, size_t bufferLen);

	// receive the content
	U8Vec peakBytes();
	std::string peakStr(
		std::string replyStartContent,
		std::string replyEndStr, U8Vec replyEndByte,
		int timeOutMS);
	std::string receiveStr(
		std::string replyStartContent,
		std::string replyEndStr, U8Vec replyEndByte,
		int timeOutMS);
	std::string receiveStr(
		std::string replyStartContent,
		std::string replyEndStr,
		int timeOutMS);
	// to trigger callback
	void simuReceive(std::string reply);
	// not tested!
	//std::string receiveStrIgnoreContentBefore(
	//	std::string replyStartContent,
	//	std::string replyEndStr, U8Vec replyEndByte,
	//	int timeOutMS);
	U8Vec receiveBytes(
		std::string replyStartContent, U8Vec replyStartBytes,
		std::string replyEndStr, U8Vec replyEndBytes,
		int timeOutMS);
	U8Vec receiveUntil(U8Vec replyEndBytes, int timeOutMS);

	std::string purgeSendAndWaitForStrReply(
		std::string content,
		std::string replyStartContent,
		std::string replyEndStr, U8Vec replyEndByte,
		int timeOutMS);

	U8Vec receiveAnyBytes();
	U8Vec getLastReceivedBytes();

	void putToBufferStart(std::string content);

	bool checkReplyStr(std::string expectedReply, int timeout, std::string* replyOut = nullptr);
	bool checkReplyU8(U8Vec expectedReply, int timeout, U8Vec* replyOut = nullptr);

	static int compareBuffers(void* buffer, size_t bufferLen, void* pattern, size_t patternLen);
	static std::vector<int> find(void* buffer, size_t bufferLen, void* pattern, size_t patternLen);

	time_t getUnavailableDuration();

	std::string getErrorHint() { return errorHint; };

	int reconnect(bool rebootDriverIfNeeded = true);
	void disconnect();
	void debugTestCallback();
	void debugAddReceive(U8Vec dat);

	struct Callback {
		// redirected to 
		int referenceIndex = -1;
		SerialCallbackMP sentCallbackMP = NULL;
		SerialCallbackMP receiveCallbackMP = NULL;
		//SerialCallback sentCallback = NULL;
		//SerialCallback receiveCallback = NULL;
	}_callback;

private:

	struct PortConfig {
		//int receiveTimeout = 10;

		HANDLE serialHandle = NULL;
		int isConfiged = 0;
		bool flagSimu = false;
		// if copy, do not close the handle at destruction
		int isHandleShallowCopy = 0;
		int sameNameIdx = -1;
		int baudRate = -1;
		int dataBitNum = -1;
		int stopBitNum = -1;
		int parity = -1;

		std::wstring portName;
		HandShakeProtocolSerial handshake;

		std::string toStr();
	}_sCommCfg;

	U8Vec receiveBuffer;
	U8Vec lastReceivedBytes;
	unsigned long lastReceiveTime = 0;
	time_t unavailableTimeStart;
	std::string errorHint;

	bool hasError(std::string* errorOut = nullptr);
	//static bool hasError(HANDLE handle, std::string* errorOut = nullptr);
	int connectToSelectedPortCore();

	int receiveToBuffer();
	bool receiveUntilMatchStartEnd(
		int offset,
		void* startKeywordPtr, int startKeywordLen,
		void* endKeywordPtr, int endKeywordLen,
		int timeout, int* matchStartIdxOut, int* matchEndIdxOut);
	// return: offset of first match
	int receiveUntilMatchBuffer(int startOffset, void* contentIn, size_t contentLen, long startTime, int timeout);
	int receiveUntilMatchBufferLoop(int startOffset, void* contentIn, size_t contentLen);

	std::string extractFromBuffer(int replyStartIdx, int replyEndIdx);
	std::string copyFromBuffer(int replyStartIdx, int replyEndIdx);

	void _sentCallback(bool success, const void* log, int logLen, const void* data, int dataLen);
	void _receiveCallback(bool success, const void* log, int logLen, const void* data, int dataLen);

};

#endif